'use strict';

import { clone } from './clone';
import { createProxy } from './Proxy';

export default class Timeline {
  public startTime: number = null;
  public lastSnapshotTime: number = -1;
  public history: any[] = [];
  public maxSnapshots: number;

  constructor(maxSnapshots: number = 10) {
    this.maxSnapshots = maxSnapshots;
  }

  public takeSnapshot(state: any, elapsedTime?: number): void {
    if (!this.startTime) {
      this.startTime = Date.now();
    }

    const now = (!elapsedTime) ? Date.now() : this.startTime + elapsedTime;
    const end = now - this.startTime;

    //
    // history timeframe:
    //
    // { "start": 0, "end": 1000, "data": {} }
    //
    this.history.push({
      data: clone(state),
      end,
      start: this.lastSnapshotTime,
    });

    // drop older history
    if (this.history.length > this.maxSnapshots) {
      this.history.shift();
    }

    this.lastSnapshotTime = end;
  }

  /**
   * Get snapshot taken at `elapsedTime` interval.
   */
  public at(elapsedTimeAt: number, interpolate: boolean = true): any {
    let i = this.history.length;
    let lesserDistance = Infinity;
    let lesserDistanceIndex = 0;

    while (i--) {
      const frame = this.history[i];
      const duration = ((frame.end - frame.start) / 2) - 1; // -1 is to prevent equal comparations
      const startDistance = Math.sqrt(Math.pow(elapsedTimeAt - frame.end - duration, 2));
      const endDistance = Math.sqrt(Math.pow(elapsedTimeAt - frame.end + duration, 2));

      if (startDistance < lesserDistance || endDistance < lesserDistance || elapsedTimeAt === frame.end) {
        lesserDistance = Math.min(startDistance, endDistance);
        lesserDistanceIndex = i;
      }
    }

    const frame = this.history[lesserDistanceIndex];
    const data = frame.data;

    //
    // traverse all properties to interpolate / extrapolate numbers
    //
    if (interpolate && elapsedTimeAt !== frame.end) {
      const previousState = this.at(frame.start, false);
      let multiplier = 1;

      if (elapsedTimeAt > frame.end) {
        //
        // extrapolation multiplier
        //
        // TODO: a better extrapolation method is needed.
        // needs to consider all previous states to evaluate a consistent next possible value
        //
        multiplier = - (elapsedTimeAt - frame.end) / (frame.end - frame.start);

      } else {
        multiplier = (elapsedTimeAt - frame.start) / (frame.end - frame.start);

      }

      return createProxy(data, previousState, multiplier);

      //
      // It may be a good idea to traverse everything at once instead of
      // lazy-evaluating through proxies
      //
      // this.traverseInterpolate( data, // previousState, multiplier )
      //
    }

    return data;
  }

  public offset(offset: number, interpolate: boolean = true): any {

    return this.at(this.lastSnapshotTime + offset, interpolate);

  }

  // traverseInterpolate ( state, previousState, multiplier ) {
  //   for ( let prop in state  ) {
  //     let propType = typeof( state[ prop ] )
  //
  //     if ( propType === "number" ) {
  //       state[ prop ] = state[prop] + ( previousState[ prop ] - state[ prop ] ) * multiplier
  //
  //     } else if ( propType === "object" || propType === "array" ) {
  //       this.traverseInterpolate( state[ prop ], previousState[ prop ], multiplier )
  //
  //     }
  //
  //   }
  // }
}
